﻿using StarryEdge.Core.Enums;
using StarryEdge.Core.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace StarryEdge.Core.Tool
{
    public static class KeyboardHook
    {
        #region Windows API Code
        [StructLayout(LayoutKind.Sequential)]
        protected class POINT
        {
            public int x;
            public int y;
        }
        [StructLayout(LayoutKind.Sequential)]
        protected class MouseHookStruct
        {
            public POINT pt;
            public int hwnd;
            public int wHitTestCode;
            public int dwExtraInfo;
        }
        [StructLayout(LayoutKind.Sequential)]
        protected class MouseLLHookStruct
        {
            public POINT pt;
            public int mouseData;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }
        [StructLayout(LayoutKind.Sequential)]
        protected class KeyboardHookStruct
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }
        [DllImport("user32.dll", CharSet = CharSet.Auto,
          CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        static extern int SetWindowsHookEx(
          int idHook,
          HookProc lpfn,
          IntPtr hMod,
          int dwThreadId);
        [DllImport("user32.dll", CharSet = CharSet.Auto,
          CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        static extern int UnhookWindowsHookEx(int idHook);
        [DllImport("user32.dll", CharSet = CharSet.Auto,
           CallingConvention = CallingConvention.StdCall)]
        static extern int CallNextHookEx(
          int idHook,
          int nCode,
          int wParam,
          IntPtr lParam);
        [DllImport("user32")]
        static extern int ToAscii(
          int uVirtKey,
          int uScanCode,
          byte[] lpbKeyState,
          byte[] lpwTransKey,
          int fuState);
        [DllImport("user32")]
        static extern int GetKeyboardState(byte[] pbKeyState);
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern short GetKeyState(int vKey);
        //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);



        protected delegate int HookProc(int nCode, int wParam, IntPtr lParam);
        const int WH_MOUSE_LL = 14;
        const int WH_KEYBOARD_LL = 13;
        const int WH_MOUSE = 7;
        const int WH_KEYBOARD = 2;
        const int WM_MOUSEMOVE = 0x200;
        const int WM_LBUTTONDOWN = 0x201;
        const int WM_RBUTTONDOWN = 0x204;
        const int WM_MBUTTONDOWN = 0x207;
        const int WM_LBUTTONUP = 0x202;
        const int WM_RBUTTONUP = 0x205;
        const int WM_MBUTTONUP = 0x208;
        const int WM_LBUTTONDBLCLK = 0x203;
        const int WM_RBUTTONDBLCLK = 0x206;
        const int WM_MBUTTONDBLCLK = 0x209;
        const int WM_MOUSEWHEEL = 0x020A;
        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_SYSKEYDOWN = 0x104;
        const int WM_SYSKEYUP = 0x105;
        const byte VK_SHIFT = 0x10;
        const byte VK_CAPITAL = 0x14;
        const byte VK_NUMLOCK = 0x90;
        const byte VK_LSHIFT = 0xA0;
        const byte VK_RSHIFT = 0xA1;
        const byte VK_LCONTROL = 0xA2;
        const byte VK_RCONTROL = 0x3;
        const byte VK_LALT = 0xA4;
        const byte VK_RALT = 0xA5;
        const byte LLKHF_ALTDOWN = 0x20;
        #endregion
        #region Private Variables
        static int _hookType = WH_KEYBOARD_LL;
        static int _handleToHook;
        static bool _isStarted = false;
        static HookProc _hookCallback;
        #endregion
        #region Properties
        public static bool IsStarted
        {
            get
            {
                return _isStarted;
            }
        }
        #endregion

        #region Methods
        public static bool Start()
        {
            if (!_isStarted &&
              _hookType != 0)
            {
                _hookCallback = new HookProc(HookCallbackProcedure);
                _handleToHook = SetWindowsHookEx(
                  _hookType,
                  _hookCallback,
                  GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName),
                  0);
                if (_handleToHook != 0)
                {
                    _isStarted = true;
                    PollingHook();
                    return true;
                }
                return false;
            }
            return true;
        }
        public static void Stop()
        {
            if (_isStarted)
            {
                UnhookWindowsHookEx(_handleToHook);
                _isStarted = false;
            }
        }

        static int HookCallbackProcedure(int nCode, Int32 wParam, IntPtr lParam)
        {
            bool handled = false;
            if (nCode > -1)
            {
                KeyboardHookStruct keyboardHookStruct =
                  (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                if (keyboardHookStruct == null)
                {
                    return -1;
                }
                bool control = ((GetKeyState(VK_LCONTROL) & 0x80) != 0) ||
                        ((GetKeyState(VK_RCONTROL) & 0x80) != 0);
                bool shift = ((GetKeyState(VK_LSHIFT) & 0x80) != 0) ||
                       ((GetKeyState(VK_RSHIFT) & 0x80) != 0);
                bool alt = ((GetKeyState(VK_LALT) & 0x80) != 0) ||
                      ((GetKeyState(VK_RALT) & 0x80) != 0);
                bool capslock = GetKeyState(VK_CAPITAL) != 0;
                switch (wParam)
                {
                    case WM_KEYDOWN:
                        if ((keyboardHookStruct.vkCode == 189) || (keyboardHookStruct.vkCode > 47 && keyboardHookStruct.vkCode < 58) || (keyboardHookStruct.vkCode > 64 && keyboardHookStruct.vkCode < 91))
                        {
                            _stopwatch.Restart();
                            _list.Add(Convert.ToChar(keyboardHookStruct.vkCode == 189 ? 45 : keyboardHookStruct.vkCode));
                        }
                        if (control)
                        {
                            var model = keyboardHooks.FirstOrDefault(x => x.Alt == alt && x.Key == Convert.ToChar(keyboardHookStruct.vkCode));
                            if (model != null)
                            {
                                model.Function?.Invoke();
                            }
                        }
                        break;
                }
            }
            if (handled)
            {
                return 1;
            }
            else
            {
                return CallNextHookEx(_handleToHook, nCode, wParam, lParam);
            }
        }
        #endregion

        private static Dictionary<int, Action<string>> _keyboardAction = new Dictionary<int, Action<string>>();

        private static List<KeyboardHookModel> keyboardHooks = new List<KeyboardHookModel>();

        public static void RegistrationListening(int length, Action<string> action) => _keyboardAction.Add(length, action);
        public static void RegistrationListening(KeyboardHookModel keyboardHook) => keyboardHooks.Add(keyboardHook);

        public static void UninstallationListening(int length)
        {
            if (_keyboardAction.ContainsKey(length))
            {
                _keyboardAction.Remove(length);
            }
        }

        private static List<char> _list = new List<char>();
        private static Stopwatch _stopwatch = new Stopwatch();
        private static void PollingHook()
        {
            Task.Run(() =>
            {
                while (_isStarted)
                {
                    System.Threading.Thread.Sleep(80);
                    try
                    {
                        if (_stopwatch.ElapsedMilliseconds <= 80 && _list.Count >= 10 && _keyboardAction.ContainsKey(_list.Count))
                        {
                            Thread.Sleep(80);
                            if (_keyboardAction.ContainsKey(_list.Count))
                            {
                                try
                                {
                                    _keyboardAction[_list.Count].Invoke(string.Join(null, _list));
                                }
                                catch (Exception)
                                {
                                }
                            }
                        }
                        else if (_stopwatch.ElapsedMilliseconds > 80 && _list.Count > 0)
                        {
                            _list.Clear();
                        }
                    }
                    catch (Exception)
                    {
                    }
                }
            });
        }
    }
}
